﻿using System;
using System.Collections;
using System.Numerics;

namespace Core
{
    public class RSA
    {
        #region MATH
        public static BigInteger GenerateN(BigInteger p, BigInteger q)
        {
            return p * q;
        }

        public static BigInteger GenerateM(BigInteger p, BigInteger q)
        {
            return (p - 1) * (q - 1);
        }

        public static bool isRelativePrime(BigInteger a, BigInteger b)
        {
            return (BigInteger.GreatestCommonDivisor(a, b) == 1);
        }


        /// <summary>
        /// Generate Decrypt Key
        /// </summary>
        /// <param name="e"></param>
        /// <param name="m"></param>
        /// <returns></returns>
        public static BigInteger GenerateDecryptKey(BigInteger e, BigInteger m)
        {
            BigInteger i = m, result = 0, d = 1;
            while (e > 0)
            {
                BigInteger t = i / e, x = e;
                e = i % x;
                i = x;
                x = d;
                d = result - t * x;
                result = x;
            }
            result %= m;
            
            if (result < 0)
            {
                result = (result + m) % m;
            }
            return result;
        }

        public static BigInteger EncryptRSA(BigInteger plain, BigInteger e, BigInteger n)
        {
            return modular_pow(plain, e, n);
        }

        public static BigInteger DecrytptRSA(BigInteger cipher, BigInteger d, BigInteger n)
        {
            return modular_pow(cipher, d, n);
        }

        public static bool isPrime(BigInteger p)
        {
            BigInteger limit = (BigInteger)Math.Sqrt((double)p) + 1;
            BigInteger i = 2;

            while (i <= limit)
            {
                if (p % i == 0) return false;
                else ++i;
            }

            return true;
        }

        public static void PrintValues(IEnumerable myList, int myWidth)
        {
            int i = myWidth;
            foreach (Object obj in myList)
            {
                if (i <= 0)
                {
                    i = myWidth;
                    Console.WriteLine();
                }
                i--;
                Console.Write("{0,8}", obj);
            }
            Console.WriteLine();
        }


        public static BigInteger modular_pow(BigInteger basis, BigInteger exponent, BigInteger modulus)
        {
            return BigInteger.ModPow(basis, exponent, modulus);
        }
        #endregion

        public static string charToIntString(char c)
        {
            if ((int)c < 10) { return "00" + Convert.ToString((int)c); }
            else if ((int)c < 100) { return "0" + Convert.ToString((int)c); }
            else return Convert.ToString((int)c);
        }

        public static string[] parseLongString(string s, int p)
        {
            int length;

            if (s.Length % p == 0) length = (s.Length / p);
            else length = (s.Length / p) + 1;
            string[] retval = new string[length];

            int j = 0;
            for (int i = 0; i < s.Length; i = i + p)
            {
                if (i + p <= s.Length)
                {
                    retval[j] = s.Substring(i, p);
                }
                else // last
                {
                    retval[j] = s.Substring(i, s.Length % p);
                    paddingLastPart(retval[j], p);
                }

                ++j;
            }

            return retval;
        }

        public static void paddingLastPart(string s, int p)
        {
            while (s.Length < p)
            {
                s = "0" + s;
            }
        }

        public static void PrintArray(string[] sArray)
        {
            foreach (string s in sArray)
            {
                Console.WriteLine(s);
            }
        }

        public static void PrintArray(BigInteger[] sArray)
        {
            foreach (BigInteger s in sArray)
            {
                Console.WriteLine(s);
            }
        }

        public static long getDigit(BigInteger p)
        {
            return p.ToString().Length;
        }

        public static BigInteger[] ConvertStringToBigInt(string[] sArray)
        {
            BigInteger[] bigIntArray = new BigInteger[sArray.Length];
            for (int i = 0; i < sArray.Length; ++i)
            {
                bigIntArray[i] = BigInteger.Parse(sArray[i]);
            }
            return bigIntArray;
        }

        public static string[] ConvertBigIntToSringArray(BigInteger[] longArray)
        {
            string[] sArray = new string[longArray.Length];
            for (int i = 0; i < longArray.Length; ++i)
            {
                sArray[i] = longArray[i].ToString();
            }
            return sArray;
        }

        public static BigInteger[] EncryptArrayBigInt(BigInteger[] plainArray, BigInteger e, BigInteger n)
        {
            BigInteger[] cipherArray = new BigInteger[plainArray.Length];
            for (int i = 0; i < plainArray.Length; ++i)
            {
                cipherArray[i] = EncryptRSA(plainArray[i], e, n);
            }
            return cipherArray;
        }

        public static BigInteger[] DecryptBigIntArray(BigInteger[] cipherArray, BigInteger d, BigInteger n)
        {
            BigInteger[] plainArray = new BigInteger[cipherArray.Length];
            for (int i = 0; i < cipherArray.Length; ++i)
            {
                plainArray[i] = DecrytptRSA(cipherArray[i], d, n);
            }
            return plainArray;
        }

        public static string ConcatStringArray(string[] sArray)
        {
            string retval = "";
            for (int i = 0; i < sArray.Length; ++i)
            {
                retval += sArray[i];
            }
            return retval;
        }

        public static string ArrayBigIntToString(BigInteger[] longASCII)
        {
            string retval = "";
            for (int i = 0; i < longASCII.Length; ++i)
            {
                retval += (char)longASCII[i];
            }
            return retval;
        }

        /// <summary>
        /// ubah dari string s, menjadi array of string yang isinya, string representasi char
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static string[] StringToASCII(string s)
        {
            string[] retval = new string[s.Length];
            for (int i = 0; i < s.Length; ++i)
            {
                retval[i] = charToIntString(s[i]);
            }
            return retval;
        }

        public static void NormalisasiString(string[] target, long num)
        {
            int length = target.Length - 1;
            int i = 0;

            for (; i < length; ++i)
            {
                while (target[i].Length < num)
                {
                    target[i] = "0" + target[i];
                }
            }

            // Last Number
            if (i > 0)
            {
                long bound = (num - ((length + 1) * num) % 3) % 3;

                while (target[i].Length < bound)
                {
                    target[i] = "0" + target[i];
                }
            }

            target[length] =  lastNormalisasi(target[length], num, target.Length);
            string sk = target[length];
        }

        public static string lastNormalisasi(string s, long num, long numString)
        {
            long temp = (num * (numString - 1))+ s.Length;
            while(temp%3 != 0)
            {
                s = "0" + s;
                temp++;
            }
            return s;
        }

        public static BigInteger[] EncryptFinalRSA(string plaintext, BigInteger e, BigInteger n)
        {
            string[] sASCII = StringToASCII(plaintext);
            string stringASCII = ConcatStringArray(sASCII);

            // Ubah plain string ke arary of string
            string[] sArray = parseLongString(stringASCII, (int)getDigit(n) - 1);

            BigInteger[] arrayLong = ConvertStringToBigInt(sArray);

            BigInteger[] longCipher = EncryptArrayBigInt(arrayLong, e, n);

            return longCipher;
        }

        public static string EncryptRSAResultString(string plaintext, BigInteger e, BigInteger n)
        {
            BigInteger[] bigInts = EncryptFinalRSA(plaintext, e, n);
            return Converter.toHex(bigInts);
        }


        public static string DecryptFinalRSA(BigInteger[] BigIntArray, BigInteger d, BigInteger n)
        {
            BigInteger[] bigIntCipher = DecryptBigIntArray(BigIntArray, d, n);

            string[] sArrayPlain = ConvertBigIntToSringArray(bigIntCipher);

            NormalisasiString(sArrayPlain, (int)getDigit(n) - 1);

            string sFull = ConcatStringArray(sArrayPlain);
            string[] sASCII = parseLongString(sFull, 3);
            BigInteger[] longASCII = ConvertStringToBigInt(sASCII);
            string retval = ArrayBigIntToString(longASCII);

            return retval;
        }
    }
}